# Session Command Control Plane (Agentic / Headless Slash)

Snow exposes many UX features as TUI slash commands (`/buddy`, `/yolo`, `/mcp`, …). Issue #190 adds a **stable control plane** so agents and scripts can call the same semantics without reverse-engineering private JSON files.

## Entry points

| Surface         | How                                             |
| --------------- | ----------------------------------------------- |
| CLI (P0)        | `snow cmd <command> [args...] [--json] [--yes]` |
| Agent tool (P1) | `session-command-list` / `session-command-run`  |
| SSE (P2)        | `POST /session/command` with JSON body          |

All three call the same `runSessionCommand()` implementation.

## CLI examples

```bash
snow cmd session-command list --json
snow cmd help --json
snow cmd buddy status --json
snow cmd buddy hatch Pip --species=fox --json
snow cmd buddy set --hat=crown --eye=✦ --color=cyan --rarity=legendary --shiny=true --json
snow cmd buddy say hello --json
snow cmd theme status --json
snow cmd theme set dark --json
snow cmd statusline status --json
snow cmd tool-display compact --json
snow cmd simple on --json
snow cmd think-display compact --json
snow cmd image-compress status --json
snow cmd hybrid-compress status --json
snow cmd speedometer on --json
snow cmd auto-format status --json
snow cmd subagent-depth 2 --json
snow cmd file-list-display tree --json
snow cmd language zh --json
snow cmd show-thinking on --json
snow cmd privacy status --yes --json
snow cmd mcp status --json
snow cmd mcp reconnect myservice --yes --json
snow cmd profiles list --json
snow cmd codebase status --json
snow cmd codebase agent-review on --yes --json
snow cmd yolo status --json
snow cmd yolo on --yes --json
snow cmd permissions status --json
snow cmd session list --json
snow cmd goal status --json
snow cmd loop list --json
snow cmd skills list --json
snow cmd config snapshot --json
snow cmd export md --json
snow cmd usage --json
snow cmd usage --period=day --json
snow cmd compact --yes --json
snow cmd reindex --yes --json
snow cmd buddy reset --yes --json
```

- `--json` — machine-readable `{ ok, command, data, code?, message?, risk? }`
- `--yes` / `--confirm` — confirm medium/high risk writes

Exit code: `0` on success, `1` on failure.

## Agent tools

Built-in service: **session-command**

1. `session-command-list` — list allowlisted commands (+ optional `risk` filter)
2. `session-command-run` — run a command

```json
{
	"command": "buddy.hatch",
	"args": "小雪 --species=fox --personality=calm",
	"confirm": false
}
```

Medium/high risk writes need `"confirm": true`.

## SSE

```http
POST /session/command
Content-Type: application/json

{
  "command": "tool-display",
  "args": "compact",
  "confirm": false
}
```

## Risk model

| Risk           | Default                        | Examples                                                                          |
| -------------- | ------------------------------ | --------------------------------------------------------------------------------- |
| `read`         | Allowed                        | buddy status, mcp status, profiles list, session list, goal status                |
| `low_write`    | Allowed                        | buddy hatch/pet/mute/say, simple, tool-display, export, goal create               |
| `medium_write` | Needs `--yes` / `confirm:true` | yolo on, plan on, profile switch, codebase on, mcp enable/disable, session resume |
| `high_risk`    | Needs confirm                  | buddy reset, permissions clear                                                    |

**Status queries** (`status` / bare / `list` / `current`) are treated as read even when the command also supports writes.

Bare command defaults (no subcommand):

| Bare              | Resolves to            |
| ----------------- | ---------------------- |
| `buddy`           | `buddy.status`         |
| `theme`           | `theme.status`         |
| `statusline`      | `statusline.status`    |
| `mcp`             | `mcp.status`           |
| `ide`             | `ide.status`           |
| `profiles`        | `profiles.list`        |
| `permissions`     | `permissions.status`   |
| `session`         | `session.list`         |
| `goal`            | `goal.status`          |
| `loop`            | `loop.list`            |
| `skills`          | `skills.list`          |
| `config`          | `config.snapshot`      |
| `session-command` | `session-command.list` |

## Command matrix

Columns: **Command ID** | **CLI form** | **Risk** | **Confirm** | **Notes**

### Buddy

| Command ID      | CLI form                                         | Risk      | Confirm | Notes                                                     |
| --------------- | ------------------------------------------------ | --------- | ------- | --------------------------------------------------------- |
| `buddy.status`  | `snow cmd buddy status`                          | read      | no      | Bare `buddy` defaults here                                |
| `buddy.hatch`   | `snow cmd buddy hatch <name> [--species=…]`      | low_write | no      | Create companion                                          |
| `buddy.pet`     | `snow cmd buddy pet`                             | low_write | no      |                                                           |
| `buddy.rename`  | `snow cmd buddy rename <name>`                   | low_write | no      |                                                           |
| `buddy.set`     | `snow cmd buddy set --hat=… --eye=… --color=… …` | low_write | no      | Customize look/color/personality; `customize` is an alias |
| `buddy.mute`    | `snow cmd buddy mute`                            | low_write | no      |                                                           |
| `buddy.unmute`  | `snow cmd buddy unmute`                          | low_write | no      |                                                           |
| `buddy.profile` | `snow cmd buddy profile …`                       | low_write | no      | list/current/set                                          |
| `buddy.reset`   | `snow cmd buddy reset --yes`                     | high_risk | yes     | Remove companion                                          |
| `buddy.species` | `snow cmd buddy species`                         | read      | no      |                                                           |
| `buddy.say`     | `snow cmd buddy say <message>`                   | low_write | no      | May call model                                            |

### Display / theme

| Command ID          | CLI form                                                  | Risk      | Confirm | Notes                                                                 |
| ------------------- | --------------------------------------------------------- | --------- | ------- | --------------------------------------------------------------------- |
| `theme.status`      | `snow cmd theme status`                                   | read      | no      | Bare `theme`; includes toolIcons / toolStatusIcons / toolDisplayNames |
| `theme.set`         | `snow cmd theme set <name\|key=value…>`                   | low_write | no      | Incl. `toolIcons=` (category/status prefixes), `toolDisplayNames=`    |
| `statusline.status` | `snow cmd statusline status`                              | read      | no      | plugins + builtins                                                    |
| `simple`            | `snow cmd simple [on\|off\|status]`                       | low_write | no      | status is read                                                        |
| `tool-display`      | `snow cmd tool-display [mode\|status]`                    | low_write | no      |                                                                       |
| `think-display`     | `snow cmd think-display [mode\|status]`                   | low_write | no      |                                                                       |
| `image-compress`    | `snow cmd image-compress [on\|off\|status]`               | low_write | no      |                                                                       |
| `hybrid-compress`   | `snow cmd hybrid-compress [on\|off\|status]`              | low_write | no      | status is read                                                        |
| `speedometer`       | `snow cmd speedometer [on\|off\|status]`                  | low_write | no      | live tracker + persist                                                |
| `subagent-depth`    | `snow cmd subagent-depth [N\|status]`                     | low_write | no      | non-negative int                                                      |
| `file-list-display` | `snow cmd file-list-display [list\|tree\|toggle\|status]` | low_write | no      |                                                                       |
| `language`          | `snow cmd language [en\|zh\|zh-TW\|status]`               | low_write | no      |                                                                       |
| `show-thinking`     | `snow cmd show-thinking [on\|off\|status]`                | low_write | no      | emits showThinking                                                    |

### Modes

| Command ID              | CLI form                                 | Risk         | Confirm | Notes            |
| ----------------------- | ---------------------------------------- | ------------ | ------- | ---------------- |
| `yolo`                  | `snow cmd yolo [on\|off\|status]`        | medium_write | yes\*   | \*status is read |
| `plan`                  | `snow cmd plan [on\|off\|status]`        | medium_write | yes\*   |                  |
| `tool-search`           | `snow cmd tool-search [on\|off\|status]` | medium_write | yes\*   |                  |
| `vulnerability-hunting` | `snow cmd vulnerability-hunting …`       | medium_write | yes\*   |                  |
| `team`                  | `snow cmd team [on\|off\|status]`        | medium_write | yes\*   |                  |
| `ultra-todo`            | `snow cmd ultra-todo …`                  | medium_write | yes\*   |                  |

### MCP / IDE / profiles / config connectivity

| Command ID          | CLI form                                                       | Risk         | Confirm | Notes                                                                                                   |
| ------------------- | -------------------------------------------------------------- | ------------ | ------- | ------------------------------------------------------------------------------------------------------- |
| `mcp.status`        | `snow cmd mcp status`                                          | read         | no      | Bare `mcp`                                                                                              |
| `mcp.reconnect`     | `snow cmd mcp reconnect <service> --yes`                       | medium_write | yes     |                                                                                                         |
| `mcp.enable`        | `snow cmd mcp enable <service\|tool> --yes`                    | medium_write | yes     |                                                                                                         |
| `mcp.disable`       | `snow cmd mcp disable <service\|tool> --yes`                   | medium_write | yes     |                                                                                                         |
| `ide.status`        | `snow cmd ide status`                                          | read         | no      | Bare `ide`                                                                                              |
| `ide.connect`       | `snow cmd ide connect [port] --yes`                            | medium_write | yes     |                                                                                                         |
| `ide.disconnect`    | `snow cmd ide disconnect --yes`                                | medium_write | yes     |                                                                                                         |
| `connection-status` | `snow cmd connection-status`                                   | read         | no      | Alias of `ide.status`                                                                                   |
| `profiles.list`     | `snow cmd profiles list`                                       | read         | no      | Bare `profiles`                                                                                         |
| `profiles.current`  | `snow cmd profiles current`                                    | read         | no      |                                                                                                         |
| `profiles.switch`   | `snow cmd profiles switch <name> --yes`                        | medium_write | yes     |                                                                                                         |
| `codebase`          | `snow cmd codebase [on\|off\|status\|agent-review\|reranking]` | medium_write | yes\*   | status is read; agent-review/reranking mutually exclusive                                               |
| `reindex`           | `snow cmd reindex [--force] --yes`                             | medium_write | yes     | Needs codebase configured                                                                               |
| `auto-format`       | `snow cmd auto-format [on\|off\|status]`                       | low_write    | no      |                                                                                                         |
| `telemetry`         | `snow cmd telemetry [on\|off\|status]`                         | medium_write | yes\*   |                                                                                                         |
| `privacy`           | `snow cmd privacy [status\|on\|off\|mode api\|local]`          | medium_write | yes\*   | no secrets; optional `scope project\|global`                                                            |
| `usage`             | `snow cmd usage [--period=...]`                                | read         | no      | Session snapshot + history; `--period` accepts hour/day/week/month and 24h/7d/30d/12m, default last_30d |

### Session automation

| Command ID           | CLI form                                                   | Risk         | Confirm | Notes                            |
| -------------------- | ---------------------------------------------------------- | ------------ | ------- | -------------------------------- |
| `compact`            | `snow cmd compact [sessionId] --yes`                       | medium_write | yes     | Needs active/target session      |
| `export`             | `snow cmd export <txt\|md\|html\|json> [sessionId] [path]` | low_write    | no      | Formats: txt, md, html, json     |
| `permissions.status` | `snow cmd permissions status`                              | read         | no      | Bare `permissions`               |
| `permissions.allow`  | `snow cmd permissions allow <tool> --yes`                  | medium_write | yes     | Always-approve tool              |
| `permissions.revoke` | `snow cmd permissions revoke <tool> --yes`                 | medium_write | yes     |                                  |
| `permissions.clear`  | `snow cmd permissions clear --yes`                         | high_risk    | yes     | Clears all always-approved tools |

### Session lifecycle

| Command ID        | CLI form                               | Risk         | Confirm | Notes                |
| ----------------- | -------------------------------------- | ------------ | ------- | -------------------- |
| `session.list`    | `snow cmd session list`                | read         | no      | Bare `session`       |
| `session.current` | `snow cmd session current`             | read         | no      |                      |
| `session.resume`  | `snow cmd session resume <id> --yes`   | medium_write | yes     |                      |
| `session.load`    | `snow cmd session load <id> --yes`     | medium_write | yes     | Alias of resume      |
| `session.branch`  | `snow cmd session branch [name] --yes` | medium_write | yes     | Fork current session |

### Goal / loop / skills

| Command ID       | CLI form                               | Risk         | Confirm | Notes                                   |
| ---------------- | -------------------------------------- | ------------ | ------- | --------------------------------------- |
| `goal.status`    | `snow cmd goal status`                 | read         | no      | Bare `goal`                             |
| `goal.create`    | `snow cmd goal create <objective>`     | low_write    | no      | Does **not** auto-start full Ralph loop |
| `goal.pause`     | `snow cmd goal pause --yes`            | medium_write | yes     |                                         |
| `goal.resume`    | `snow cmd goal resume --yes`           | medium_write | yes     |                                         |
| `goal.clear`     | `snow cmd goal clear --yes`            | medium_write | yes     |                                         |
| `loop.list`      | `snow cmd loop list`                   | read         | no      | Bare `loop`                             |
| `loop.create`    | `snow cmd loop create <spec> --yes`    | medium_write | yes     |                                         |
| `loop.cancel`    | `snow cmd loop cancel <id> --yes`      | medium_write | yes     |                                         |
| `loop.tasks`     | `snow cmd loop tasks`                  | read         | no      |                                         |
| `skills.list`    | `snow cmd skills list`                 | read         | no      | Bare `skills`                           |
| `skills.status`  | `snow cmd skills status [name]`        | read         | no      |                                         |
| `skills.enable`  | `snow cmd skills enable <name> --yes`  | medium_write | yes     |                                         |
| `skills.disable` | `snow cmd skills disable <name> --yes` | medium_write | yes     |                                         |

### Help / config / meta

| Command ID             | CLI form                        | Risk | Confirm | Notes                          |
| ---------------------- | ------------------------------- | ---- | ------- | ------------------------------ |
| `help`                 | `snow cmd help`                 | read | no      | Top commands + examples        |
| `config.snapshot`      | `snow cmd config snapshot`      | read | no      | Bare `config`; non-secret only |
| `home`                 | `snow cmd home`                 | read | —       | Always `HEADLESS_UNSUPPORTED`  |
| `session-command.list` | `snow cmd session-command list` | read | no      | Full allowlist                 |

## Error codes

| Code                    | Meaning                                        |
| ----------------------- | ---------------------------------------------- |
| `UNKNOWN_COMMAND`       | Not in allowlist                               |
| `COMMAND_NOT_ALLOWED`   | Policy blocked                                 |
| `CONFIRMATION_REQUIRED` | Need `--yes` / `confirm:true`                  |
| `INVALID_ARGS`          | Bad arguments                                  |
| `HEADLESS_UNSUPPORTED`  | Not available outside TUI (e.g. `home`)        |
| `EXECUTION_FAILED`      | Runtime failure                                |
| `NOT_FOUND`             | Missing resource (buddy/profile/session/skill) |
| `ALREADY_EXISTS`        | e.g. buddy already hatched                     |
| `NOT_CONFIGURED`        | e.g. codebase embedding missing                |
| `SESSION_REQUIRED`      | Active/target session required (e.g. compact)  |

## Success shape

```json
{
	"ok": true,
	"command": "buddy.hatch",
	"data": {
		"companion": {
			"name": "Pip",
			"species": "fox",
			"rarity": "rare"
		}
	},
	"message": "Pip hatched as a rare fox.",
	"risk": "low_write"
}
```

## Migration from private JSON

Prefer `snow cmd …` (or agent `session-command-run` / SSE `POST /session/command`) over hand-editing `~/.snow/*.json`.

| Old / private approach                                                              | Prefer                                                             |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------- |
| Edit `~/.snow/buddy.json` hatch/status/reset/look                                   | `buddy hatch` / `buddy status` / `buddy set` / `buddy reset --yes` |
| Trigger buddy reply via private AI helpers                                          | `buddy say <message>`                                              |
| Edit theme / simple / tool-display / toolIcons / status prefixes / toolDisplayNames | `theme status` / `theme set` (incl. `toolIcons=on                  | off`, `toolIcons=status:on | off`, `toolIcons=status:success:✓`, `toolDisplayNames=<tool>:<name>`), `simple`, `tool-display`, `think-display` |
| Flip yolo/plan/tool-search in settings                                              | `yolo` / `plan` / `tool-search` with `--yes` for writes            |
| Manually edit always-approved tools                                                 | `permissions status                                                | allow                      | revoke                                                                                                           | clear`     |
| Toggle MCP services in config JSON                                                  | `mcp status                                                        | enable                     | disable                                                                                                          | reconnect` |
| Toggle skills disable lists                                                         | `skills list                                                       | status                     | enable                                                                                                           | disable`   |
| Read raw settings for agent automation                                              | `config snapshot` / `config status` (non-secret only)              |
| Hand-edit `config.json` / `profiles/*.json` limits or models                        | `config set maxContextTokens=… maxTokens=… advancedModel=…` (hot)  |
| Hand-create/delete/rename profile files                                             | `profiles create` / `delete` / `rename` (writes need `--yes`)      |

`config set` keys: `maxContextTokens`, `maxTokens`, `advancedModel`, `basicModel`, `requestMethod` (chat|responses|gemini|anthropic). **Does not** set `apiKey`.

Still **do not** document or automate silent API key mutation through this plane.

## Notes / intentional limits

- `home` is TUI navigation only → `HEADLESS_UNSUPPORTED`.
- `goal create` records the objective; it does **not** auto-start the full Ralph loop (continue via existing goal tools).
- `export` formats: `txt` | `md` | `html` | `json`.
- `usage` returns session `contextUsage` (legacy-compatible) plus rolling-window `history`; optional `--period`/`-p`/bare token, default `week` (last_30d). Data source: `~/.snow/usage`.
- `reindex` needs codebase configured or returns `NOT_CONFIGURED` / `EXECUTION_FAILED`.
- `reindex` headless path **awaits the full rebuild** (not a fire-and-forget job queue).
- Medium/high writes need `--yes` / `confirm:true` unless the call is a status-only query.
- `config snapshot` includes non-secret fields such as modes, theme, `speedometerEnabled`, `hybridCompressEnabled`, `autoFormatEnabled`, `subAgentMaxSpawnDepth`, `fileListDisplayMode`, `language`, `showThinking`, `privacy:{enabled,mode}`, and `codebaseFlags`.
- Destructive git / arbitrary shell / silent API-key changes remain **out of allowlist**.

## Dual path policy

Snow currently has two execution surfaces for many slash-like features:

| Surface                                        | Role                                             |
| ---------------------------------------------- | ------------------------------------------------ |
| TUI slash handlers (`source/utils/commands/*`) | UI-facing: messages, remount actions, i18n       |
| Control plane (`runSessionCommand`)            | Domain-first JSON contract for CLI / Agent / SSE |

**Policy**

- Shared **domain APIs** (theme/config/session/goal/loop/skills/permissions, …) are the single source of business truth.
- Plane handlers must not reimplement private file formats when a domain helper already exists.
- Full merge of TUI slash into plane is **incremental** — do not force TUI rewrite just for dedupe.
- `sessionCommandParity.ts` records expected plane ↔ TUI name overlap so silent drift is testable.
- Plane config writes emit `configEvents` so same-process TUI subscribers refresh immediately.
- Hot-sync covered (same-process): `simple`, `tool-display`, `think-display`, `show-thinking`, `image-compress`, `hybrid-compress`, `speedometer`, `auto-format`, `yolo`, `plan`, `tool-search`, `vulnerability-hunting`, `team`, `ultra-todo`, `theme`/`diffOpacity`/`customColors`/`toolIcons` (incl. status prefixes / `toolStatusIcons`) /`toolDisplayNames`, `language`, `file-list-display`, `privacy`, `telemetry`, `codebase` (+ flags), `subagent-depth`, **`config set` (apiConfig)**.
- External cross-process `snow cmd` does **not** hot-refresh an already-open TUI process; restart/reload is required for that process to load new allowlist code as well.
- Plane bare args for toggles are **status** (not TUI bare-toggle).

## Hardening / testing notes

- Medium/high risk commands require `confirm:true` / `--yes`; status/list/current queries stay free.
- Contract tests in `source/test/session-command-plane.test.ts` cover:
  - confirmation matrix for write commands
  - stable failure codes (`INVALID_ARGS`, `NOT_FOUND`, `SESSION_REQUIRED`, …)
  - reversible writes (theme toolDisplay, permissions, skills, goal) with finally-restore
  - allowlist integrity (no unexpected `HEADLESS_UNSUPPORTED` except `home`)
  - risk metadata sanity + plane/TUI overlap inventory
  - same-process `configEvents` emission for plan/yolo/theme and matrix toggles (hybrid-compress/speedometer/language/...)
- Integrity probes use `confirm:false` so reindex/compact never run for real during matrix tests.

## What not to do

- Do **not** hand-edit `~/.snow/buddy.json` / theme files as the primary automation path.
- Do **not** parse minified `bundle/cli.mjs` for private APIs.
- Destructive git/filesystem power-ups and silent API-key changes remain out of this allowlist.

## Related

- [25.Buddy Companion Guide](./25.Buddy%20Companion%20Guide.md)
- [12.Headless Mode](./12.Headless%20Mode.md)
- [20.SSE Service Mode](./20.SSE%20Service%20Mode.md)
- [28.Official Docs Tools snow-docs](./28.Official%20Docs%20Tools%20snow-docs.md)
